<?php

/**
 * Library :: DAL (Data Access Layer)
 *
 * Warlock is free software. This version may have been modified pursuant
 * to the GNU General Public License, and as distributed it includes or
 * is derivative of works licensed under the GNU General Public License or
 * other free or open source software licenses.
 *
 * @package    warlock
 * @author     David Cole <neophyte@sourcetutor.com>
 * @license    GNU/GPL v2
 */

// Object Dependencies
using('Engine.Config');
using('Engine.Error');
using('Engine.Profiling');

class DAL extends Object
{
    /**
     * Instance to DAO
     *
     * @var object DAL_DAO
     * @access public
     */
    var $DAO;

    /**
     * Data Objects Array
     *
     * @var array
     * @access public
     */
    var $DataObjects;


    /**#@+
     * Required Core Objects
     *
     * These are references to the core objects required by this
     * class.
     *
     * @access private
     * @var    object
     */
    var $_Error;
    var $_Config;
    var $_Profiler;
    var $_Model;
    /**#@-*/

    /**
     * Data Access Layer Constructor
     *
     * Performs general object startup tasks such as getting instances to the required
     * core objects and initialising DAO.
     *
     * @return void
     * @access public
     */
    function __construct()
    {
        // Add To Vault
        Vault::Add('DAL', &$this);

        // Get References to required objects
        $this->_Error    = &Vault::Get('Debug_Error');
        $this->_Profiler = &Vault::Get('Debug_Profiling');
        $this->_Config   = &Vault::Get('Config');
        $this->_Model    = &Vault::Get('Model');

        $this->_Profiler->Start('Initialise Data Access Layer');

        // Initialise DAO
        $dao_conf = $this->_Config->Get('Database');
        $class    = 'DAL_DAO_'.$dao_conf['interface'];

        using('Engine.DAL.DAO.'.$dao_conf['interface']);
        $this->DAO = new $class();

        // Default Data Objects
        $this->_LoadDataObject('node');
        
        $this->_Profiler->Stop();
    }

    /**
     * Select
     *
     * Attempts to retrieve a result set of the given Data Object
     * based on the given paramaters.
     *
     *
     * Where Conditions :
     * An associative array of attributes, the key is the field name
     * which can contain upto 3 attributes. An exception is if you just pass
     * a sing primary key.
     *
     *  - 'co'    Default[=]      Comparison Operator of which '=', '!=', '<', '>', '<=', '>=' are considered valid
     *  - 'value'                 The value to be used for the comparison, varies depending on type
     *  - 'type'  Default[string] The type of value of which 'string', 'int', 'func', 'field' are considered valid
     *  - 'func'  Optional        Functions such as CONCAT etc
     *
     * Examples :-
     *
     * Select(12)
     * Select(array('field' => 'value'))
     * Select(array('field' => array('!=', 'some_value')))
     * Select(array('field' => array('<=', 'some_field', 'field')))
     * Select(array('field' => array('<=', null, 'func', 'NOW')))
     *
     *
     * @param   string  $do       Data Object
     * @param   array   $where    Associative array of where conditions
     * @param   array   $order    Associative array of field orders
     * @param   int     $rows     Number of rows to retrieve
     * @param   int     $offset   Offset from which rows should be retrieved
     * @param   string  $mode     How returned rows should be formatted
     *
     * @return object             DAL_DAO_ResultSet false on failure
     * @access public
     */
    function Select($do, $where = array(), $order = array(), $rows = false, $offset = false, $mode = 'DOBJECT')
    {
        if (!$this->_LoadDataObject($do))
            return false;

        return $this->DataObjects[$do]->Select($where, $order, $rows, $offset, $mode);
    }

    /**
     * Insert
     *
     * Attempts to create a new record within the given Data Object
     * using the supplied data.
     *
     * @param   string  $do    Data Object
     * @param   array   $data  Associative array of field->value pairs
     * @param   bool    $log   Whether to log the Data Object action or not
     *
     * @return int             ID of inserted record (0 of none available) false on failure
     * @access public
     */
    function Insert($do, $data = array(), $log = false)
    {
        // Ensure the data object is loaded
        if (!$this->_LoadDataObject($do))
            return false;

        // Santitize the data
        if (!isset($data['_PROCESSED']) || !$data['_PROCESSED'])
            if (($data = $this->_ValidateFieldSet($do, $data)) === false)
                return false;
            
        // Run the query 
        if (($id = $this->DataObjects[$do]->Insert($data)) !== false)
            return $id;
        else
            return false;            
    }

    /**
     * Update
     *
     * Attempts to update records within a Data Object using the
     * given data and matching against the where array.
     *
     * @param   string  $do     Data Object
     * @param   array   $data   Associative array of field->value pairs
     * @param   array   $where  Associative array of where conditions
     * @param   bool    $log    Whether to log the Data Object action or not
     *
     * @return int              Number of affected records or false or failure.
     * @access public
     */
    function Update($do, $data = array(), $where = array(), $log = false)
    {
        if (!$this->_LoadDataObject($do))
            return false;

        if (($data = $this->_ValidateFieldSet($do, $data)) !== false)
        {
            if (($affected = $this->DataObjects[$do]->Update($data, $where)) !== false)
            {
                return $affected;
            }
            else
                return false;
        }
        else
            return false;
    }

    /**
     * Delete
     *
     * Attempts to delete record within the specified Data Object based
     * on the given where conditions.
     *
     * @param   string  $do     Data Object
     * @param   array   $where  Associative array of where conditions
     * @param   bool    $log    Whether to log the Data Object action or not
     *
     * @return int              Number of affected records or false or failure.
     * @access public
     */
    function Delete($do, $where = array(), $log = false)
    {
        if (!$this->_LoadDataObject($do))
            return false;

        if (($affected = $this->DataObjects[$do]->Delete($where)) !== false)
        {
            return $affected;
        }
        else
            return false;
    }

    /**
     * Validate Field Set
     *
     * Validates the supplied data against the given
     * field definitions.
     *
     * @param   array   $fields  Associative array of field definitions
     * @param   array   $data    Associative array of field->value pairs
     *
     * @return array             Validated data array or false upon failure
     * @access private
     */
    function _ValidateFieldSet($do, $data)
    {
        $fields = array();
        
        // Fields relating to the specified data object
        foreach ($this->DataObjects[$do]->Fields as $field => $attributes)
            $fields[$field] = $attributes;
            
        // Fields joined to the data object
        for ($i = 0, $imax = count($this->DataObjects[$do]->Relationships); $i < $imax; $i++)
            foreach ($this->DataObjects[$do]->Relationships[$i]['fields'] as $actual => $alias)
                $fields[$alias] = $this->DataObjects[$this->DataObjects[$do]->Relationships[$i]['do']]->Fields[$actual];
                                        
        foreach ($data as $field => $value)
        {
            if (isset($fields[$field]))
            {
                if (($ret = $this->_ValidateField($fields[$field], $value)) === false)
                    $this->_Error->Log('DAL : DO['.$do.'] :: Field \''.$field.'\' failed validation with content \''.$value.'\'');
                else
                    $data[$field] = $this->_ValidateField($fields[$field], $value);
            }
        }

        return $data;               
    }
    
    function _ValidateField($attributes, $value)
    {
        // Validate value against the field type
        if (isset($attributes['type']))
        {
            switch ($attributes['type'])
            {
                case 'STRING':
                    $value = '\''.$this->DAO->EscapeString($value).'\'';
                    break;
                case 'INTEGER':
                    if (!is_numeric($data))
                    {
                        $error = true;

                    }
                    break;
                case 'NUMERIC': break;
                    if (!is_numeric($value))
                    {
                        $error = true;

                    }
                case 'FLOAT': break;
                    if (!is_numeric($value))
                    {
                        $error = true;

                    }
                case 'DATE':
                    $value = '\''.$this->DAO->EscapeString($value).'\'';
                    break;
                case 'TIME':
                    $value = '\''.$this->DAO->EscapeString($value).'\'';
                    break;
                case 'DATETIME':
                    $value = '\''.$this->DAO->EscapeString($value).'\'';
                    break;
                case 'TIMESTAMP': break;
                    $value = '\''.$this->DAO->EscapeString($value).'\'';
                case 'BOOLEAN':
                    $value = (($value)?$attributes['true']:$attributes['false']);
                    break;
                case 'BLOB':
                    $value = '\''.$this->DAO->EscapeString($value).'\'';
                    break;
                case 'ENUM': break;
            }
        }

        // Validate value against field size
        if (!is_numeric($attributes['size']))
        {
        	switch ($attributes['size'])
         	{
         		case 'LONG'   : $attributes['size'] = (2^32);
         		case 'MEDIUM' : $attributes['size'] = (2^24);
         		case 'INT'    : $attributes['size'] = (2^16);
         		case 'TINY'   : $attributes['size'] = (2^8);
          	}
        }
                
        if (isset($attributes['size']))
        {
            if (strlen($value) > ((int)$attributes['size']))
            {
                $error = true;
            }
        }

        return $value;        
    }

    /**
     * Load Data Object
     *
     * @param   string  $do  Data Object
     *
     * @return bool
     * @access public
     */
    function _LoadDataObject($do)
    {
        if (isset($this->DataObjects[$do]))
            return true;
        else
        {
            if (!class_exists($class = ('DAL_DataObject_'.$do)))
            {
                if (file_exists( $file = W_ENV_PATH.'/Engine/DAL/DataObjects/'.$do.'.php'))
                    include_once $file;
                else
                {
                    $this->_Error->Log('DAL :: Error location data object \''.$do.'\' expected at \''.$file.'\'');
                    return false;
                }
            }

            $this->DataObjects[$do] = new $class();

            if ($this->DataObjects->Relationships)
                foreach ($this->DataObjects->Relationships as $relation)
                    if (!$this->_LoadDataObject($relation['do']))
                        return false;

            return true;
        }
    }
}

?>